import numpy as np


class FeatureEngineer:
    """
    特征工程器 - 负责所有特征计算和数据转换
    """

    def extract_features(self, student_data):
        """
        为单个学生提取完整特征集

        Args:
            student_data: 包含学生原始数据的字典
                - historical_scores: 历史成绩列表 [85, 90, 78, ...]
                - subject_full_score: 科目满分
                - student: 学生对象（可选）
                - subject: 科目对象（可选）
                - exam_records: 考试记录详情（可选）

        Returns:
            features: 包含所有特征的字典
        """
        features = {}

        # 获取基础数据
        scores = student_data.get('historical_scores', [])
        full_score = student_data.get('subject_full_score', 150)

        # 1. 基础统计特征
        features.update(self._extract_basic_statistics(scores, full_score))

        # 2. 趋势和变化特征
        features.update(self._extract_trend_features(scores, full_score))

        # 3. 稳定性和波动特征
        features.update(self._extract_stability_features(scores, full_score))

        # 4. 相对表现特征
        features.update(self._extract_performance_features(features, full_score))

        # 5. 时间相关特征（如果有时序数据）
        if 'exam_records' in student_data:
            features.update(self._extract_time_features(student_data['exam_records']))

        # 6. 为规则预测保持兼容性
        features['statistics'] = {
            'avg_score': features['avg_score'],
            'max_score': features['max_score'],
            'min_score': features['min_score'],
            'recent_score': features['recent_score'],
            'exam_count': features['exam_count']
        }

        return features

    def _extract_basic_statistics(self, scores, full_score):
        """提取基础统计特征"""
        if not scores:
            base_score = full_score * 0.6
            return {
                'avg_score': base_score,
                'max_score': base_score,
                'min_score': base_score,
                'recent_score': base_score,
                'exam_count': 0,
                'total_exams': 0,
                'first_score': base_score,
                'last_score': base_score
            }

        return {
            'avg_score': float(np.mean(scores)),
            'max_score': float(np.max(scores)),
            'min_score': float(np.min(scores)),
            'recent_score': float(scores[0]),  # 最近一次成绩
            'exam_count': len(scores),
            'total_exams': len(scores),
            'first_score': float(scores[-1]),  # 最早一次成绩
            'last_score': float(scores[0])  # 最近一次成绩（与recent_score相同）
        }

    def _extract_trend_features(self, scores, full_score):
        """提取趋势和变化特征"""
        features = {}

        if len(scores) < 2:
            features.update({
                'score_trend': 0.0,
                'improvement_rate': 0.0,
                'momentum': 0.0,
                'acceleration': 0.0,
                'is_improving': 0,
                'is_declining': 0
            })
            return features

        # 线性趋势斜率
        x = np.arange(len(scores))
        y = np.array(scores)
        slope = self._linear_regression_slope(x, y)

        # 最近趋势（最近3次）
        recent_trend = 0.0
        if len(scores) >= 3:
            recent_x = np.arange(3)
            recent_y = np.array(scores[:3])
            recent_trend = self._linear_regression_slope(recent_x, recent_y)

        # 进步速率（最近成绩相对于平均成绩）
        improvement_rate = (scores[0] - np.mean(scores)) / full_score if full_score > 0 else 0

        features.update({
            'score_trend': float(slope),
            'recent_trend': float(recent_trend),
            'improvement_rate': float(improvement_rate),
            'momentum': float(scores[0] - scores[1] if len(scores) > 1 else 0),
            'acceleration': float(recent_trend - slope),  # 趋势变化率
            'is_improving': 1 if slope > 0 else 0,
            'is_declining': 1 if slope < 0 else 0,
            'trend_strength': float(abs(slope) / full_score if full_score > 0 else abs(slope))
        })

        return features

    def _extract_stability_features(self, scores, full_score):
        """提取稳定性和波动特征"""
        features = {}

        if len(scores) < 2:
            features.update({
                'volatility': 0.0,
                'stability_ratio': 0.0,
                'consistency_score': 1.0,
                'max_drawdown': 0.0,
                'recovery_rate': 0.0,
                'is_volatile': 0
            })
            return features

        # 计算波动性
        volatility = float(np.std(scores))

        # 稳定性比率（波动范围相对于满分）
        score_range = np.max(scores) - np.min(scores)
        stability_ratio = score_range / full_score if full_score > 0 else score_range

        # 一致性评分（1 - 变异系数）
        cv = volatility / np.mean(scores) if np.mean(scores) > 0 else 0
        consistency_score = max(0, 1 - cv)

        # 最大回撤（从高点下跌的最大幅度）
        max_drawdown = self._calculate_max_drawdown(scores)

        # 恢复率（回撤后恢复的能力）
        recovery_rate = self._calculate_recovery_rate(scores)

        features.update({
            'volatility': volatility,
            'stability_ratio': float(stability_ratio),
            'consistency_score': float(consistency_score),
            'max_drawdown': float(max_drawdown),
            'recovery_rate': float(recovery_rate),
            'is_volatile': 1 if stability_ratio > 0.3 else 0,
            'score_range': float(score_range),
            'cv_score': float(cv)  # 变异系数
        })

        return features

    def _extract_performance_features(self, existing_features, full_score):
        """提取相对表现特征"""
        avg_score = existing_features['avg_score']
        max_score = existing_features['max_score']
        recent_score = existing_features['recent_score']

        return {
            'performance_ratio': avg_score / full_score if full_score > 0 else 0,
            'recent_performance_ratio': recent_score / full_score if full_score > 0 else 0,
            'max_performance_ratio': max_score / full_score if full_score > 0 else 0,
            'potential_ratio': (max_score - avg_score) / full_score if full_score > 0 else 0,
            'efficiency_score': recent_score / max_score if max_score > 0 else 1.0,
            'is_underperforming': 1 if avg_score < full_score * 0.6 else 0,
            'is_overperforming': 1 if avg_score > full_score * 0.8 else 0
        }

    def _extract_time_features(self, exam_records):
        """提取时间相关特征"""
        features = {}

        if not exam_records or len(exam_records) < 2:
            features.update({
                'exam_frequency': 0.0,
                'days_between_exams_avg': 0.0,
                'recent_exam_interval': 0.0,
                'exam_regularity': 0.0
            })
            return features

        try:
            # 计算考试时间间隔
            time_intervals = []
            for i in range(1, len(exam_records)):
                if hasattr(exam_records[i], 'exam') and hasattr(exam_records[i - 1], 'exam'):
                    current_date = exam_records[i].exam.exam_date
                    previous_date = exam_records[i - 1].exam.exam_date
                    interval = (current_date - previous_date).days
                    time_intervals.append(interval)

            if time_intervals:
                features.update({
                    'exam_frequency': float(1 / np.mean(time_intervals)) if np.mean(time_intervals) > 0 else 0,
                    'days_between_exams_avg': float(np.mean(time_intervals)),
                    'days_between_exams_std': float(np.std(time_intervals)),
                    'recent_exam_interval': float(time_intervals[0] if time_intervals else 0),
                    'exam_regularity': float(1 - (np.std(time_intervals) / np.mean(time_intervals))) if np.mean(
                        time_intervals) > 0 else 0
                })
            else:
                features.update({
                    'exam_frequency': 0.0,
                    'days_between_exams_avg': 0.0,
                    'days_between_exams_std': 0.0,
                    'recent_exam_interval': 0.0,
                    'exam_regularity': 0.0
                })

        except (AttributeError, TypeError):
            # 如果时间数据不可用，设置默认值
            features.update({
                'exam_frequency': 0.0,
                'days_between_exams_avg': 0.0,
                'days_between_exams_std': 0.0,
                'recent_exam_interval': 0.0,
                'exam_regularity': 0.0
            })

        return features

    def _linear_regression_slope(self, x, y):
        """计算线性回归斜率"""
        if len(x) < 2:
            return 0.0

        x = np.array(x)
        y = np.array(y)

        n = len(x)
        sum_x = np.sum(x)
        sum_y = np.sum(y)
        sum_xy = np.sum(x * y)
        sum_x2 = np.sum(x * x)

        denominator = n * sum_x2 - sum_x * sum_x
        if denominator == 0:
            return 0.0

        slope = (n * sum_xy - sum_x * sum_y) / denominator
        return float(slope)

    def _calculate_max_drawdown(self, scores):
        """计算最大回撤"""
        if len(scores) < 2:
            return 0.0

        peak = scores[0]
        max_drawdown = 0.0

        for score in scores[1:]:
            if score > peak:
                peak = score
            else:
                drawdown = (peak - score) / peak if peak > 0 else 0
                max_drawdown = max(max_drawdown, drawdown)

        return float(max_drawdown)

    def _calculate_recovery_rate(self, scores):
        """计算恢复率"""
        if len(scores) < 3:
            return 0.0

        # 简单实现：计算从低点恢复的次数比例
        recovery_count = 0
        total_dips = 0

        for i in range(1, len(scores) - 1):
            if scores[i] < scores[i - 1]:  # 出现下跌
                total_dips += 1
                if scores[i + 1] > scores[i]:  # 下一次恢复
                    recovery_count += 1

        return float(recovery_count / total_dips) if total_dips > 0 else 0.0

    def get_feature_categories(self):
        """返回特征分类信息"""
        return {
            'basic_statistics': [
                'avg_score', 'max_score', 'min_score', 'recent_score',
                'exam_count', 'total_exams', 'first_score', 'last_score'
            ],
            'trend_features': [
                'score_trend', 'recent_trend', 'improvement_rate', 'momentum',
                'acceleration', 'is_improving', 'is_declining', 'trend_strength'
            ],
            'stability_features': [
                'volatility', 'stability_ratio', 'consistency_score',
                'max_drawdown', 'recovery_rate', 'is_volatile', 'score_range', 'cv_score'
            ],
            'performance_features': [
                'performance_ratio', 'recent_performance_ratio', 'max_performance_ratio',
                'potential_ratio', 'efficiency_score', 'is_underperforming', 'is_overperforming'
            ],
            'time_features': [
                'exam_frequency', 'days_between_exams_avg', 'days_between_exams_std',
                'recent_exam_interval', 'exam_regularity'
            ]
        }

    def validate_features(self, features):
        """验证特征数据的合理性"""
        try:
            required_categories = self.get_feature_categories()
            all_expected_features = []
            for category in required_categories.values():
                all_expected_features.extend(category)

            # 检查必需特征是否存在
            missing_features = set(all_expected_features) - set(features.keys())
            if missing_features:
                print(f"⚠️ 缺少特征: {missing_features}")

            # 检查特征值范围
            for key, value in features.items():
                if isinstance(value, (int, float)):
                    if np.isnan(value) or np.isinf(value):
                        features[key] = 0.0
                        print(f"⚠️ 修复异常值: {key} = {value}")

            return True

        except Exception as e:
            print(f"❌ 特征验证失败: {str(e)}")
            return False